# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

import json
import google.protobuf.json_format as json_format
import coin.exchange.base.kr_rest.private_client_base as privcb
import coin.exchange.kraken.kr_rest.native_private_client as nprivc
import coin.exchange.kraken.kr_rest.product as exchange_product
from coin.exchange.kraken.kr_rest.currency import (
    KrakenCurrency,
    remove_native_currency_prefix,
)
from coin.exchange.kraken.kr_rest.product import KrakenProduct
from coin.exchange.kraken.kr_rest.product_info import kraken_product_info
from coin2.exchange.order.system_pb2 import OrderSystemConfig
from coin2.exchange.symbology_pb2 import ProductConfig
from cc.appcoin2.support.order.py_parser import PyOrderParser

import coin.proto.coin_order_enums_pb2 as coin_order
from coin.proto.coin_query_pb2 import (AccountBalance,
                                       CurrencyBalance,
                                       ProductFillElement,
                                       ProductFillBundle,
                                       ProductOrderElement,
                                       ProductOrderBundle)


class KrakenPrivateParser(object):
  @staticmethod
  def parse_balance(update_msg):
    """
    {'error': [], 'result': {'XXBT': '0.9864062590', 'XXRP': '50.00000000'}}
    """
    assert len(update_msg['error']) == 0, update_msg
    currency_balance_list = []
    for native_currency, balance in update_msg['result'].items():
      balance = float(balance)
      native_currency = remove_native_currency_prefix(native_currency)
      currency = KrakenCurrency.FromStrNativeCurrency(native_currency)
      currency_balance = CurrencyBalance(
          currency=currency.currency,
          currency_native=currency.native_currency,
          available=balance,
          hold=0,
          total=balance,
      )
      currency_balance_list.append(currency_balance)

    return AccountBalance(exchange='Kraken', market_type='Spot', each_balance=currency_balance_list)

  @staticmethod
  def parse_fill(update_msg, product) -> ProductFillBundle:
    assert len(update_msg['error']) == 0, update_msg
    currency_fill_list = []
    for trade_id, trade_info in update_msg['result']['trades'].items():
      pair = exchange_product.remove_xz_from_rest_native_symbol(trade_info['pair'])
      if product.native_symbol != pair:  # skip
        continue

      if trade_info['type'] == 'sell':
        side = coin_order.SELL_ORDER
      elif trade_info['type'] == 'buy':
        side = coin_order.BUY_ORDER
      else:
        raise ValueError(trade_info['type'])

      currency_fill = ProductFillElement(symbol=product.symbol,
                                         side=side,
                                         price=float(trade_info['price']),
                                         qty=float(trade_info['vol']),
                                         order_id=trade_info['ordertxid'])
      currency_fill_list.append(currency_fill)

    return ProductFillBundle(exchange='Kraken', symbol=product.symbol, each_fill=currency_fill_list)

  @staticmethod
  def parse_orders(update_msg, product):
    assert len(update_msg['error']) == 0, update_msg
    currency_order_list = []
    for exchange_order_id, order_info in update_msg['result']['open'].items():
      order_side, qty, pair, at_sign, order_type, price = order_info['descr']['order'].split()
      if pair != product.native_symbol:  # skip
        continue

      assert at_sign == '@', order_info
      if order_side == 'ask':
        side = coin_order.SELL_ORDER
      elif order_side == 'buy':
        side = coin_order.BUY_ORDER
      else:
        raise ValueError(order_side)

      currency_order = ProductOrderElement(symbol=product.symbol,
                                           side=side,
                                           price=float(price),
                                           qty=float(qty),
                                           order_id=exchange_order_id,
                                           created_timestamp=int(
                                               float(order_info['opentm']) * 1e+9))
      currency_order_list.append(currency_order)
    return ProductOrderBundle(exchange='Kraken',
                              symbol=product.symbol,
                              each_order=currency_order_list)

  @staticmethod
  def parse_fee_rate(update_msg):
    """
    {
      "error": [],
    "result": {
      "currency": "ZUSD",
      "volume": "5169774.7782",
      "fees": {
        "XXBTZUSD": {
          "fee": "0.1200",
          "minfee": "0.1000",
          "maxfee": "0.2600",
          "nextfee": "0.1000",
          "nextvolume": "10000000.0000",
          "tiervolume": "5000000.0000"
        },
        "XETHZEUR": {
          "fee": "0.1200",
          "minfee": "0.1000",
          "maxfee": "0.2600",
          "nextfee": "0.1000",
          "nextvolume": "10000000.0000",
          "tiervolume": "5000000.0000"
        }
      },
      "fees_maker": {
        "XXBTZUSD": {
          "fee": "0.0200",
          "minfee": "0.0000",
          "maxfee": "0.1600",
          "nextfee": "0.0000",
          "nextvolume": "10000000.0000",
          "tiervolume": "5000000.0000"
        },
        "XETHZEUR": {
          "fee": "0.0200",
          "minfee": "0.0000",
          "maxfee": "0.1600",
          "nextfee": "0.0000",
          "nextvolume": "10000000.0000",
          "tiervolume": "5000000.0000"
        }
      }
    }
  }
    """
    assert (not update_msg['error'])
    fee_table = {}
    for native_symbol, info in update_msg['result']['fees'].items():
      product = KrakenProduct.FromStrNativeProductNothrow(native_symbol)
      if product is None:
        continue
      fee_table[product] = {'taker': float(info['fee']) / 100.}

    for native_symbol, info in update_msg['result']['fees_maker'].items():
      product = KrakenProduct.FromStrNativeProductNothrow(native_symbol)
      if product is None:
        continue
      fee_table[product]['maker'] = float(info['fee']) / 100.

    return fee_table


class KrakenPrivateClient(privcb.PrivateClientBase):
  ProductType = exchange_product.KrakenProduct

  def __init__(self, key_file):
    privcb.PrivateClientBase.__init__(self)
    self.nprivc = nprivc.KrakenNativePrivateClient(key_file=key_file)
    pi_bundle = kraken_product_info._product_infos
    assert pi_bundle.HasField('mea')
    oss_config = OrderSystemConfig(
      mea=pi_bundle.mea,
      products=ProductConfig(norms=[pi.symbol for pi in pi_bundle.product_infos]))
    self._parser = PyOrderParser.from_mea_str(pi_bundle.mea)
    self._parser.add_product(oss_config.SerializeToString())

  def query_account_balance_impl(self):
    update = self.nprivc.query_account_balance()
    msg = self._parser.parse_account_balance(json.dumps(update.msg))
    account_balance = AccountBalance()
    json_format.Parse(msg, account_balance)
    update.msg = account_balance
    return update

  def query_fills_impl(self, product):
    update = self.nprivc.query_trades_history()
    update.msg = KrakenPrivateParser.parse_fill(update.msg, product)
    return update

  def query_list_orders_impl(self, product):
    update = self.nprivc.query_open_orders()
    update.msg = KrakenPrivateParser.parse_orders(update.native_msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    raise NotImplementedError()

  def query_cancel_product_impl(self, product):
    raise NotImplementedError()


if __name__ == '__main__':
  client = KrakenPrivateClient(key_file='../../coin_key/view/xunke02/kraken/view_key.json')
  print(client.query_account_balance_impl())
